// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/quic/quic_chromium_alarm_factory.h"

#include "net/quic/test_tools/mock_clock.h"
#include "net/quic/test_tools/test_task_runner.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace net {
namespace test {
    namespace {

        class TestDelegate : public QuicAlarm::Delegate {
        public:
            TestDelegate()
                : fired_(false)
            {
            }

            void OnAlarm() override { fired_ = true; }

            bool fired() const { return fired_; }
            void Clear() { fired_ = false; }

        private:
            bool fired_;
        };

        class QuicChromiumAlarmFactoryTest : public ::testing::Test {
        protected:
            QuicChromiumAlarmFactoryTest()
                : runner_(new TestTaskRunner(&clock_))
                , alarm_factory_(runner_.get(), &clock_)
            {
            }

            scoped_refptr<TestTaskRunner> runner_;
            QuicChromiumAlarmFactory alarm_factory_;
            MockClock clock_;
        };

        TEST_F(QuicChromiumAlarmFactoryTest, CreateAlarm)
        {
            TestDelegate* delegate = new TestDelegate();
            std::unique_ptr<QuicAlarm> alarm(alarm_factory_.CreateAlarm(delegate));

            QuicTime::Delta delta = QuicTime::Delta::FromMicroseconds(1);
            alarm->Set(clock_.Now().Add(delta));

            // Verify that the alarm task has been posted.
            ASSERT_EQ(1u, runner_->GetPostedTasks().size());
            EXPECT_EQ(base::TimeDelta::FromMicroseconds(delta.ToMicroseconds()),
                runner_->GetPostedTasks()[0].delay);

            runner_->RunNextTask();
            EXPECT_EQ(QuicTime::Zero().Add(delta), clock_.Now());
            EXPECT_TRUE(delegate->fired());
        }

        TEST_F(QuicChromiumAlarmFactoryTest, CreateAlarmAndCancel)
        {
            TestDelegate* delegate = new TestDelegate();
            std::unique_ptr<QuicAlarm> alarm(alarm_factory_.CreateAlarm(delegate));

            QuicTime::Delta delta = QuicTime::Delta::FromMicroseconds(1);
            alarm->Set(clock_.Now().Add(delta));
            alarm->Cancel();

            // The alarm task should still be posted.
            ASSERT_EQ(1u, runner_->GetPostedTasks().size());
            EXPECT_EQ(base::TimeDelta::FromMicroseconds(delta.ToMicroseconds()),
                runner_->GetPostedTasks()[0].delay);

            runner_->RunNextTask();
            EXPECT_EQ(QuicTime::Zero().Add(delta), clock_.Now());
            EXPECT_FALSE(delegate->fired());
        }

        TEST_F(QuicChromiumAlarmFactoryTest, CreateAlarmAndReset)
        {
            TestDelegate* delegate = new TestDelegate();
            std::unique_ptr<QuicAlarm> alarm(alarm_factory_.CreateAlarm(delegate));

            QuicTime::Delta delta = QuicTime::Delta::FromMicroseconds(1);
            alarm->Set(clock_.Now().Add(delta));
            alarm->Cancel();
            QuicTime::Delta new_delta = QuicTime::Delta::FromMicroseconds(3);
            alarm->Set(clock_.Now().Add(new_delta));

            // The alarm task should still be posted.
            ASSERT_EQ(1u, runner_->GetPostedTasks().size());
            EXPECT_EQ(base::TimeDelta::FromMicroseconds(delta.ToMicroseconds()),
                runner_->GetPostedTasks()[0].delay);

            runner_->RunNextTask();
            EXPECT_EQ(QuicTime::Zero().Add(delta), clock_.Now());
            EXPECT_FALSE(delegate->fired());

            // The alarm task should be posted again.
            ASSERT_EQ(1u, runner_->GetPostedTasks().size());

            runner_->RunNextTask();
            EXPECT_EQ(QuicTime::Zero().Add(new_delta), clock_.Now());
            EXPECT_TRUE(delegate->fired());
        }

        TEST_F(QuicChromiumAlarmFactoryTest, CreateAlarmAndResetEarlier)
        {
            TestDelegate* delegate = new TestDelegate();
            std::unique_ptr<QuicAlarm> alarm(alarm_factory_.CreateAlarm(delegate));

            QuicTime::Delta delta = QuicTime::Delta::FromMicroseconds(3);
            alarm->Set(clock_.Now().Add(delta));
            alarm->Cancel();
            QuicTime::Delta new_delta = QuicTime::Delta::FromMicroseconds(1);
            alarm->Set(clock_.Now().Add(new_delta));

            // Both alarm tasks will be posted.
            ASSERT_EQ(2u, runner_->GetPostedTasks().size());

            // The earlier task will execute and will fire the alarm->
            runner_->RunNextTask();
            EXPECT_EQ(QuicTime::Zero().Add(new_delta), clock_.Now());
            EXPECT_TRUE(delegate->fired());
            delegate->Clear();

            // The latter task is still posted.
            ASSERT_EQ(1u, runner_->GetPostedTasks().size());

            // When the latter task is executed, the weak ptr will be invalid and
            // the alarm will not fire.
            runner_->RunNextTask();
            EXPECT_EQ(QuicTime::Zero().Add(delta), clock_.Now());
            EXPECT_FALSE(delegate->fired());
        }

        TEST_F(QuicChromiumAlarmFactoryTest, CreateAlarmAndUpdate)
        {
            TestDelegate* delegate = new TestDelegate();
            std::unique_ptr<QuicAlarm> alarm(alarm_factory_.CreateAlarm(delegate));

            QuicTime start = clock_.Now();
            QuicTime::Delta delta = QuicTime::Delta::FromMicroseconds(1);
            alarm->Set(clock_.Now().Add(delta));
            QuicTime::Delta new_delta = QuicTime::Delta::FromMicroseconds(3);
            alarm->Update(clock_.Now().Add(new_delta),
                QuicTime::Delta::FromMicroseconds(1));

            // The alarm task should still be posted.
            ASSERT_EQ(1u, runner_->GetPostedTasks().size());
            EXPECT_EQ(base::TimeDelta::FromMicroseconds(delta.ToMicroseconds()),
                runner_->GetPostedTasks()[0].delay);

            runner_->RunNextTask();
            EXPECT_EQ(QuicTime::Zero().Add(delta), clock_.Now());
            EXPECT_FALSE(delegate->fired());

            // Move the alarm forward 1us and ensure it doesn't move forward.
            alarm->Update(clock_.Now().Add(new_delta),
                QuicTime::Delta::FromMicroseconds(2));

            ASSERT_EQ(1u, runner_->GetPostedTasks().size());
            EXPECT_EQ(base::TimeDelta::FromMicroseconds(
                          new_delta.Subtract(delta).ToMicroseconds()),
                runner_->GetPostedTasks()[0].delay);
            runner_->RunNextTask();
            EXPECT_EQ(start.Add(new_delta), clock_.Now());
            EXPECT_TRUE(delegate->fired());

            // Set the alarm via an update call.
            new_delta = QuicTime::Delta::FromMicroseconds(5);
            alarm->Update(clock_.Now().Add(new_delta),
                QuicTime::Delta::FromMicroseconds(1));
            EXPECT_TRUE(alarm->IsSet());

            // Update it with an uninitialized time and ensure it's cancelled.
            alarm->Update(QuicTime::Zero(), QuicTime::Delta::FromMicroseconds(1));
            EXPECT_FALSE(alarm->IsSet());
        }

    } // namespace
} // namespace test
} // namespace net
